<?php

namespace App\Modules\LuckDraw\LuckDraw;

use App\Http\Model\FinanceDiamond;
use App\Http\Model\FinanceUserWallet;
use App\Http\Model\GiftBag;
use App\Http\Model\Hint;
use App\Http\Model\Mall;
use App\Http\Services\FinanceServices;
use App\Modules\LuckDraw\Award\AwardFactory;
use App\Modules\LuckDraw\Exp\ExpInterface;
use App\Modules\LuckDraw\Message\MessageInterface;
use App\Modules\LuckDraw\Models\AwardLogModel;
use App\Modules\LuckDraw\Models\AwardPoolModel;
use App\Modules\LuckDraw\Models\LuckDrawModel;
use App\Modules\LuckDraw\Models\UserPropLogModel;
use App\Modules\LuckDraw\Strategy\ManualStrategy;
use App\Modules\LuckDraw\Strategy\StrategyFactory;
use App\Modules\LuckDraw\Strategy\StrategyInterface;
use App\Modules\LuckDraw\User\LuckUser;
use App\Modules\LuckDraw\UserTag\UserTagInterface;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class LuckDrawBase {
    protected $luckDrawModel;
    protected $strategyInstance;
    protected $userTagInstance;
    protected $luckUserInstance;
    protected $expInstance;
    protected $messageInstance;
    protected $giftBag;
    protected $finance_service;
    protected $mall_model;

    protected $rankingRedisKey = 'LUCKDRAW:CACHE:RANKING';
    protected $latestAwardsRedisKey = 'LUCKDRAW:CACHE:LATESTAWARDS';

    public function __construct(
        LuckDrawModel $luckDrawModel, //保存抽奖活动的基本信息
        ?StrategyInterface $strategyInstance, //策略实例
        ?UserTagInterface $userTagInstance,//用户分类标签实例
        ?LuckUser $luckUserInstance,//抽奖用户实例
        ?ExpInterface $expInstance, //该抽奖活动的经验值实例
        ?MessageInterface $messageInstance //消息实例用于发送消息
    ){
        $this->luckDrawModel = $luckDrawModel;
        $this->strategyInstance = $strategyInstance;
        $this->userTagInstance = $userTagInstance;
        $this->luckUserInstance = $luckUserInstance;
        $this->expInstance = $expInstance;
        $this->messageInstance = $messageInstance;
        $this->giftBag = new GiftBag();
        $this->finance_service = new FinanceServices();
        $this->mall_model = new Mall();
    }

    /**
     * 抽奖
     **/
    //保存重置抽奖次数
    public function run(int $num = 0):?array{
        try {
            //重置抽奖次数因为有可能道具不足100连或者10连
            $num =$this->resetNum($num);

            //是否有手动发的，如果有先拿手动发送的，（通过后台配置）
            $manualStrategy = new ManualStrategy(
                $this->luckDrawModel->id,
                $this->luckUserInstance->userId(),
                $this->strategyInstance->getPoolData()
            );
            //后台配置的奖品
            $manualAwards = $manualStrategy->run($num);
            
            //抽奖，返回奖品
            $awards = $this->strategyInstance->run($num - count($manualAwards));
            $awards = $this->mergeAwards($manualAwards, $awards);
            
            //如果抽奖得到的奖品数量小于抽奖次数 //返回一个网络忙的错误
            if (empty($awards) || $this->awardCount($awards) != $num){
                //抽奖失败，记录抽奖失败日志
                write_log("LuckDraw", "LuckDraw Exception",[$this->getLuckId(), $this->luckUserInstance->userId(),"奖品数量和抽奖次数不符"]);
                $this->strategyInstance->rollBack();
                return null;
            }

            //获取奖品详情
            $awards = $this->awardsDetail($awards);
            if ($awards == null) {
                write_log("LuckDraw", "LuckDraw Exception",  [$this->getLuckId(), $this->luckUserInstance->userId(),"获取奖品详情失败"]);
                $this->strategyInstance->rollBack();
                return null;
            }

            //开启事务
            DB::beginTransaction();

            //扣除用户相应资产
            if ($this->deductProp($num)) {
                //抽奖失败 道具不足
                write_log("LuckDraw", "LuckDraw Exception",[$this->getLuckId(), $this->luckUserInstance->userId(),"道具不足"]);
                DB::rollBack();
                $this->strategyInstance->rollBack();
                return null;
            }

            //TODO 以下可以通过消息队列执行
            //将奖品保存到用户背包里
            $ok = $this->saveAwards($awards);
            if (!$ok) {
                DB::rollBack();
                $this->strategyInstance->rollBack();
                write_log("LuckDraw", "LuckDraw Exception",[$this->getLuckId(), $this->luckUserInstance->userId(),"将奖品保存到用户背包时失败"]);
                return null;
            }

            //TODO 记得检查 //增加用户经验值
            $ok = $this->addExpNum($num);
            if (!$ok) {
                DB::rollBack();
                $this->strategyInstance->rollBack();
                write_log("LuckDraw", "LuckDraw Exception",  [$this->getLuckId(), $this->luckUserInstance->userId(),"增加经验值失败"]);
                return null;
            }

            //保存抽奖日志
            $drawLogId = $this->drawLog($num, $awards);
            //设置手动发放奖品已经发送
            $ok = $manualStrategy->setHasBeenSent($drawLogId);
            if (!$ok) {
                DB::rollBack();
                $this->strategyInstance->rollBack();
                write_log("LuckDraw", "LuckDraw Exception",  [$this->getLuckId(), $this->luckUserInstance->userId(),"更新手动发放状态字段失败"]);
            }

            //增加荣耀令牌
            // $this->addGlory($num);

            //发送消息
            $this->sendMsg($awards);
            //排序
            $ok = usort($awards, function ($a, $b){
                return ($a->price<$b->price)?1:-1;
            });
            if (!$ok) {
                $awards = null;
            }

            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            $this->strategyInstance->rollBack();
            write_log("LuckDraw", "LuckDraw Exception",  [$this->getLuckId(), $this->luckUserInstance->userId(),$e->getMessage()]);
            return null;
        }
        //返回
        return $awards;
    }

    //保存抽奖日志
    public function drawLog($num, $awards){
        //抽奖日志 这个日志可用于后期做异步，以及查询哪些奖品是这次抽奖获得的
        $drawLogId = $this->luckUserInstance->drawLogSucess($num, $awards, $this->strategyInstance->poolId(), $this->strategyInstance->name(),'奖品发放成功');
        //礼物日志，一个礼品id 一条日志，可以用来做统计，比如排行榜，以及对账等
        $this->luckUserInstance->logAwards($awards,$drawLogId, $this->strategyInstance->poolId(), $this->strategyInstance->name());
        return $drawLogId;
    }

    //保存重置抽奖次数
    public function resetNum($num){
        $availProp = $this->luckUserInstance->getProp();
        if ($num < $availProp && $availProp > 0 && $num > 0) {
            return $num;
        }
        return $availProp;
    }

    public function awardsDetail($awards){
        foreach($awards as $award){
            $award->detail();
        }
        return $awards;
    }

    public function addExpNum($num):bool{
        if ($this->hasExp() && $this->getExpAdd() > 0) {
            return $this->expInstance->addExpNum( $num * $this->getExpAdd(), $this->luckUserInstance->userId());
        }
        return true;
    }

    public function getExpAdd():int{
        return $this->luckDrawModel->exp_add??0;
    }

    //保存奖品到用户背包
    public function saveAwards($awards): bool{
        return $this->luckUserInstance->saveAwards($awards);
    }

    //额外赠送荣耀令 //TODO type待定
    public function addGlory(?int $num){
        // $propModel = new Prop();
        // if(true === env('OPEN_GLORY', false)) {
        //     $propModel->grantProp($this->luckUserInstance->userId() ,$propModel->glory_prop_id,0 ,$num * $this->getGloryAdd() ,5);
        // }
    }

    //获取抽一次奖，增加多少荣耀令
    public function getGloryAdd(): int{
        return $this->luckDrawModel->glory_add??0;
    }

    //发送相关消息
    public function sendMsg(?array $awards = null){
        $data = [
            'room_id' => $this->luckUserInstance->roomId(),
            'user' => [
                'user_id' =>$this->luckUserInstance->userId(),
                'nickname' =>$this->luckUserInstance->nickname(),
            ],
            'awards' => $awards,
        ];
        $this->messageInstance->send($data);
    }

    public function getLuckId():int {
        return $this->luckDrawModel->id;
    }

    public function getTag():string {
        return $this->luckDrawModel->tag;
    }

    public function getStrategyPool():object {
        return json_decode($this->luckDrawModel->strategy_pool);
    }

    //抽奖前验证是否可以抽奖
    public function check():array {
        //检查抽奖活动是否已经开始
        if (!$this->isStart()) {
            return [Hint::LUCK_DRAW_NOT_STARTED, $this->getName().'未开始'];
        }
        //检查经验值是否达到要求
        if ($this->hasExp() && !$this->expEnough()) {
            return [Hint::LUCK_DRAW_EXP_NOT_ENOUGH, $this->getExpName() . '达到' . $this->getExpNeed() . '，才能开启' . $this->getName()];
        }

        $userPropAvail = $this->luckUserInstance->getProp();
        if ($userPropAvail <= 0){
            return [Hint::LUCK_DRAW_PROP_NOT_ENOUGH, $this->getPropName() . '数量不足'];
        }

        return [0, ''];
    }

    //打印日志

    //判断活动是否已经开始
    public function isStart(): bool {
        $now = Carbon::now();
        if ($now->gte($this->getStart()) && $now->lte($this->getEnd())) {
            return true;
        }
        return false;
    }

    //获取开始时间
    public function getStart() {
        return $this->luckDrawModel->start;
    }

    //获取开始时间
    public function getEnd() {
        return $this->luckDrawModel->end;
    }

    //获取抽奖活动的名字
    public function getName() {
        return $this->luckDrawModel->name;
    }

    //判断该抽奖活动是否有经验值
    public function hasExp():bool{
        return $this->expInstance != null;
    }

    //经验值的名字
    public function getExpName():string{
        return $this->expInstance == null?'':$this->expInstance->getName();
    }

    public function awardList():?array {
        //拿出奖池Id
        list($strategyData, $poolId) = $this->userTagInstance->getStrategyPool();
        if (empty($strategyData) || empty($poolId)) {
            return null;
        }
        //根据奖池id 获取奖池数据
        $pool = AwardPoolModel::get($poolId);
        $awards = json_decode($pool->pool);
        $retArr = [];
        foreach ($awards as $award) {
            $a = AwardFactory::get(
                $this->getLuckId(),
                $this->luckUserInstance->userId(),
                $award->type,
                $award->id,
                $award->time,
                $award->price
            );
            $a->detail();
            $retArr[] = [
                'name' => $a->name,
                'price' => $a->price,
                'image' => $a->image,
                'aperture' => $a->aperture,
            ];
        }

        usort($retArr, function ($a, $b) {
            return ($a['price'] > $b['price'])?-1:1;
        });
        return $retArr;
    }

    //获取当前用户经验值的数量
    public function getExpNum():string{
        if ($this->expInstance != null) {
            return $this->expInstance->getExpNum($this->luckUserInstance->userId(), $this->luckUserInstance->roomId());
        }
        return 0;
    }

    //判断是否有足够的经验值
    public function expEnough(): bool {
        if ($this->getExpNeed()) {
            return $this->expInstance->enough($this->getExpNeed(), $this->luckUserInstance->userId());
        }
        return true;
    }

    //获取该抽奖活动需要的经验值
    public function getExpNeed(): int {
        return $this->luckDrawModel->exp_need??0;
    }

    public function getPropName(): string {
        return $this->luckDrawModel->prop_name??'';
    }

    public function addProp(int $num, int $type = UserPropLogModel::USER_PROP_TYPE_EXCHANGE):bool{
        return $this->luckUserInstance->addProp($num, $type);
    }

    public function deductProp(int $num, int $type = UserPropLogModel::USER_PROP_TYPE_DRAW):bool {
        return $this->luckUserInstance->deductProp($num, $type) <= 0;
    }

    //奖品列表中奖品的数量
    private function awardCount($awards){
        $cnt = 0;
        foreach($awards as $award){
            $cnt = $cnt + $award->num;
        }
        return $cnt;
    }

    // $awards1 是手动配置的奖品  $awards2 是正常抽奖的奖品
    public function mergeAwards($awards1=[], $awards2=[]){
        if (empty($awards1)) {
            return $awards2;
        }
        $awards = [];
        foreach ($awards2 as $award2) {
            $awards[$award2->type . '-' . $award2->awardId . '-' . $award2->timeLimit] = $award2;
        }
        foreach ($awards1 as $award1) {
            $k = $award1->type . '-' . $award1->awardId . '-' . $award1->timeLimit;
            if (isset($awards[$k])) {
                $awards[$k]->num += $award1->num;
            } else {
                $awards[$k] = $award1;
            }
        }
        return $awards;
    }
    /**
     * 中奖记录
     * */
    public function ranking(int $top): ?array {
        //如果有缓存
        $rankingCache = $this->getRankingCache();
        if (!empty($rankingCache)) {
            return json_decode($rankingCache,true);
        }

        $ranking = AwardLogModel::ranking($this->getLuckId(), $top);;
        $awardArr = []; //缓存已经查询过的奖品；
        $ret = [];
        foreach ($ranking as $rank) {
            $k =  $rank->type . '-' . $rank->award_id . '-' . $rank->time_limit;
            if (!isset($awardArr[$k])) {
                $award = AwardFactory::get($rank->luck_id, $rank->user_id, $rank->type, $rank->award_id, $rank->time_limit, $rank->price);
                $award->detail();
                $awardArr[$k] = $award;
            }

            $a = clone $awardArr[$k];
            $a->num = $rank->num;
            $a->createdAt = $rank->created_at->format('Y-m-d H:i:s');
            $a->nickname = $rank->nickname;
            $portrait = json_decode($rank->portrait, true);
            $a->avatar = empty($portrait)?'':build_image($portrait[0]);
            array_push($ret, $a);
        }
        //重新排序
        $ok = usort($ret, function ($a, $b){
            if ($a->price < $b->price) {
                return 1;
            }
            if ($a->price == $b->price) {
                return $a->createdAt < $b->createdAt?1:-1;
            }
            return -1;
        });
        if ($ok) {
            $this->setRankingCache(json_encode($ret));
        }
        return $ok?$ret:null;
    }

    public function latestAwards(int $top): array {
        return  AwardLogModel::latestAwards($this->getLuckId(), $top);
    }

    //获取排行榜缓存
    public function getRankingCache(){
        return Redis::GET($this->rankingRedisKey . ':' . $this->getLuckId());
    }

    //设置缓存排行榜 1分钟刷新
    public function setRankingCache(string $data){
        return Redis::SET($this->rankingRedisKey . ':' . $this->getLuckId(), $data, 'EX', 60);
    }

    //获取最新中奖记录缓存
    public function getLatestAwardsCache(){
        return Redis::GET($this->latestAwardsRedisKey . ':' . $this->getLuckId());
    }

    //缓存最新中奖记录 1分钟刷新
    public function setLatestAwardsCache(string $data){
        return Redis::SET($this->latestAwardsRedisKey . ':' . $this->getLuckId(), $data, 'EX', 60);
    }

    //总送抽奖道具用
    protected $_presentType;
    public function setPresentType(int $type){
        $this->_presentType = $type;
    }
    public function getPresentType(){
        return $this->_presentType;
    }
    public function present(int $num) {

        try {
            //开启事务，扣除钻石，增加抽奖道具
            DB::beginTransaction();
            //增加道具 //3000一下不许young
            if (empty($this->getPresentType()) && $this->getPresentType() < 3000) {
                DB::rollBack();
                return [Hint::ERR_SYS_FAIL, null];
            }
            $ok =  $this->addProp($num, $this->getPresentType());
            //增加道具失败
            if (!$ok) {
                DB::rollBack();
                return [Hint::ERR_SYS_FAIL, null];
            }
            //事务提交
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            return [Hint::ERR_SYS_FAIL, null];
        }
        //返回成功
        $propNum = $this->luckUserInstance->getProp();
        $walletData = (new FinanceUserWallet())->get_wallet($this->luckUserInstance->userId());
        $diamonNum = $walletData['diamond_balance'];
        return [Hint::SUCCESS, [
            'diamond'=>$diamonNum,
            'prop' =>$propNum
        ]];
    }

    //兑换道具
    public function exchange(?int $num) : array {
        try {
            //获取所需钻石总额
            $needTotalDiamond = $this->getPrice() * $num;
            //查询当前用户可用钻石
            $walletData = (new FinanceUserWallet())->get_wallet($this->luckUserInstance->userId());
            if (empty($walletData['diamond_balance'] ?? 0) or $walletData['diamond_balance'] < $needTotalDiamond) {
                return [Hint::DIAMOND_NOT_ENOUGH, null];
            }

            //开启事务，扣除钻石，增加抽奖道具
            DB::beginTransaction();
            //扣除钻石
            $ok = (new FinanceDiamond())->inc($this->luckUserInstance->userId(), 0, 0, 6, -$needTotalDiamond , '兑换' . $this->getPropName());
            //扣除钻石失败
            if (!$ok){
                DB::rollBack();
                return [Hint::ERR_SYS_FAIL, null];
            }
            //增加道具
            $ok =  $this->addProp($num);
            //增加道具失败
            if (!$ok) {
                DB::rollBack();
                return [Hint::ERR_SYS_FAIL, null];
            }
            //赠送头像框
            $goodId = 13;
            $goodInfo = $this->mall_model->getMallGoodByGoodsId($goodId);
            if(empty($goodInfo)) {
                return [Hint::ERR_NORMAL, null];
            }
            $skuList = $this->mall_model->getSkuDataByGoods($goodId);
            if(empty($skuList[0])) {
                 return [Hint::ERR_NORMAL, null];
            }
            $goodMap[$goodId]['goods_name'] = $goodInfo['goods_name'];
            $goodMap[$goodId]['goods_type'] = $goodInfo['goods_type'];
            $goodMap[$goodId]['sku_id'] = $skuList[0]['sku_id'];
            //有效期
            $timeLimit = $num*3600;
            //商品增加
            $this->mall_model->shoppingAct(
                11,
                $this->luckUserInstance->userId(),
                $goodId,
                $goodMap[$goodId]['goods_name'],
                $goodMap[$goodId]['goods_type'],
                $goodMap[$goodId]['sku_id'],
                $timeLimit,
                0,
                $timeLimit,
                time(),
                2
            );
            //事务提交
            DB::commit();
        } catch (\Exception $e) {
            DB::rollBack();
            return [Hint::ERR_SYS_FAIL, null];
        }
        //返回成功
        $propNum = $this->luckUserInstance->getProp();
        $walletData = (new FinanceUserWallet())->get_wallet($this->luckUserInstance->userId());
        $diamonNum = $walletData['diamond_balance'];
        return [Hint::SUCCESS, [
            'diamond'=>$diamonNum,
            'prop' =>$propNum
        ]];
    }

    //获取用户资产
    public function banlance():?array{
        $propNum = $this->luckUserInstance->getProp();
        $expNum = $this->getExpNum();
        return [
            'prop' => $propNum,
            'exp' => $expNum
        ];
    }

    //获取当前抽奖规则
    public function rule():?string {
        return $this->luckDrawModel->rule??'';
    }

    //中奖记录
    public function record(int $page_num = 0, int $page_size = 20): array {
        $recordArr = []; //保存已经存在的奖品信息
        $ret = []; //返回的奖品数组
        $records = $this->luckUserInstance->record($page_num, $page_size);
        foreach ($records as $record) {
            $k =  $record->type . '-' . $record->id . '-' . $record->time_limit;
            if (!isset($recordArr[$k])) {
                $award = AwardFactory::get($record->luck_id,$record->user_id, $record->type, $record->award_id, $record->time_limit, $record->price);
                $award->detail();
                $recordArr[$k] = $award;
            }

            $a = clone $recordArr[$k];
            $a->num = $record->num;
            $a->createdAt = $record->created_at->format('Y-m-d H:i:s');
            array_push($ret, $a);
        }
        return $ret;
    }

    public function marquee(int $top = 50):?array{
        //如果有缓存
        $rankingCache = $this->getLatestAwardsCache();
        if (!empty($rankingCache)) {
            return json_decode($rankingCache,true);
        }

        $ranking = $this->latestAwards($top);
        $awardArr = []; //缓存已经查询过的奖品；
        $ret = [];
        foreach ($ranking as $rank) {
            $k =  $rank->type . '-' . $rank->award_id . '-' . $rank->time_limit;
            if (!isset($awardArr[$k])) {
                $award = AwardFactory::get($rank->luck_id, $rank->user_id, $rank->type, $rank->award_id, $rank->time_limit, $rank->price);
                $award->detail();
                $awardArr[$k] = $award;
            }

            $a = clone $awardArr[$k];
            $a->num = $rank->num;
            $a->nickname = $rank->nickname;
            array_push($ret, $a);
        }
        $this->setLatestAwardsCache(json_encode($ret));
        return $ret;
    }

    //获取价格
    public function getPrice(): int{
        return $this->luckDrawModel->price??0;
    }

    //获取额外赠送商品skuID
    public function getSkuId(): int{
        return $this->luckDrawModel->sku_id??0;
    }

    //修改奖池后，根据修改后的奖池数据更新zb_luck_draw 的strategy_pool字段
    public function updateStrategyPoolByPoolData(array $poolData){
        list($needUpdate, $strategyPool) = $this->userTagInstance->getUpdatedUserTagByPoolData($poolData);
        $result = true;
        if ($needUpdate) {
            $result = LuckDrawModel::updateStrategyPool($this->getLuckId(), $strategyPool);
        }
        return $result;
    }

    //修改奖池后，根据修改后的奖池数据清除相应策略的缓存
    public function clearCacheByPoolData(int $id){
        //获取当前用户标签所有的策略数据
        [$allStrategy, $userList] = $this->userTagInstance->getAllStrategyAndUserIds();
        //循环清除所有策略被修改奖池波及到的缓存
        foreach ($allStrategy as $strategy) {
            $strategyClass = StrategyFactory::getStategyClassName($strategy);
            if ($strategyClass != null) {
                $strategyClass::clearCacheByPoolId($this->getLuckId(),$userList, $id);
            }
        }
    }
}